package br.com.felinelayer.client;

import br.com.felinelayer.common.Communication;
import br.com.felinelayer.common.ICommunicationListener;
import br.com.felinelayer.common.Message;
import br.com.felinelayer.common.Protocol;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Classe do lado cliente (leve) Conecta ao server Camada entre socket e janela
 *
 * @author Vanius Roberto Bittencourt
 */
public abstract class Client implements ICommunicationListener {

    private static Integer getKeyByValue(Map<Integer, IComponentClient> map, IComponentClient value) {
        for (Entry<Integer, IComponentClient> entry : map.entrySet()) {
            if (value.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        return null;
    }
    private HashMap<Integer, IComponentClient> components = new HashMap<>();
    private Socket socket;
    private String applicationName;
    private IClientFactory clientFactory;
    private boolean disconnected = false;

    private void initialize(String[] args) {
        clientFactory = doCreateClientFactory();
        String parameters = "";
        String ip = "localhost:7000";
        for (int i = 0; i < args.length; i++) {
            if (i == 0) {
                ip = args[0];
            } else {
                if (!parameters.isEmpty()) {
                    parameters = parameters + "";
                }
                parameters = parameters + args[i];
            }
        }
        if (parameters.isEmpty()) {
            parameters = "sample";
        }
        applicationName = parameters;
        int port = 7000;
        String[] ips = ip.split(":");
        ip = ips[0];
        if (ips.length > 1) {
            port = Integer.parseInt(ips[1]);
        }
        try {
            // cria o socket com o recurso desejado na porta especificada
            socket = new Socket(ip, port);
        } catch (Exception e) {
            System.out.println("Algum problema ocorreu ao criar ou enviar dados pelo socket.\n"
                    + e.getMessage());
            return;
        }
        communication = new Communication(this);
        communication.isServer = false;
    }

    private int framesCount() {
        int result = 0;
        for (Integer c : components.keySet()) {
            if (components.get(c) instanceof IFrameClient) {
                result++;
            }
        }
        return result;
    }

    private void createComponent(Message message) throws NumberFormatException {
        String compName = message.getValueByName(Protocol.COMPONENT_CLASS);
        // Inialine component
        IComponentClient component = null;
        // Frame/window
        if (compName.equals(Protocol.COMPONENT_FRAME)) {
            component = clientFactory.createFrame();
        }
        // Edit
        if (compName.equals(Protocol.COMPONENT_EDIT)) {
            component = clientFactory.createEdit();
        }
        // ComboBox
        if (compName.equals(Protocol.COMPONENT_COMBOBOX)) {
            component = clientFactory.createComboBox();
        }
        // Label
        if (compName.equals(Protocol.COMPONENT_LABEL)) {
            component = clientFactory.createLabel();
        }
        // Button
        if (compName.equals(Protocol.COMPONENT_BUTTON)) {
            component = clientFactory.createButton();
        }
        // Grid
        if (compName.equals(Protocol.COMPONENT_GRID)) {
            component = clientFactory.createGrid();
        }
        // If not created component
        if (component == null) {
            return;
        }
        int id = message.getIntByName(Protocol.COMPONENT_ID);
        //System.out.println( compName + " id: " + id );
        // Add new component to component list
        components.put(new Integer(id), component);
        // Define id component
        component.setId(id);
        component.setClient(this);
        // 
        component.initialize();
        // If exists parent frame id add component to the this frame
        if (message.isPropertyExists(Protocol.PARENT_FRAME_ID)) {
            String sFrameId = message.getValueByName(Protocol.PARENT_FRAME_ID);
            if (!sFrameId.isEmpty()) {
                IComponentClient c = getComponentById(Integer.parseInt(sFrameId));
                if (c instanceof IFrameClient) {
                    IFrameClient f = (IFrameClient) c;
                    f.addComponent(component);
                    component.setFrame(f);
                }
            }
        }
    }
    protected Communication communication = null;

    protected abstract IClientFactory doCreateClientFactory();

    protected abstract void doShowMessage(String value);

    protected abstract String doShowInput(String value);

    protected void doFireMessageReceived(Message message) {
    }

    protected void destroyCallBack(IComponentClient component) {
        removeComponent(component);
    }

    public void removeComponent(IComponentClient cm) {
        components.remove(new Integer(cm.getId()));
        if ((cm instanceof IFrameClient) && (framesCount() == 0)) {
            disconnected = true;
            communication.disconnect();
        }
    }

    public Client() {
        String[] params = {"default"};
        initialize(params);
    }

    public Client(String[] args) {
        initialize(args);
    }

    @Override
    public Socket getSocket() {
        return socket;
    }

    // TODO: create event e sendEvent must be moved to Client object (server don't use)
    public Message createEvent(IComponentClient component, String eventName) throws ComponentIDNotFoundException {
        Message m = new Message(Protocol.MSG_NAME_EVENT);
        m.setValueByName(Protocol.EVENT_NAME, eventName);
        int id = getIdByComponent(component);
        if (id == 0) {
            String warning = "Client.createEvent: component " + this.toString()
                    + " no id specified";
            throw new ComponentIDNotFoundException(warning);
        }
        m.setValueByName(Protocol.EVENT_COMPONENT_ID, id);
        return m;
    }

    public void sendEvent(IComponentClient component, String eventName) throws ComponentIDNotFoundException {
        Message m = createEvent(component, eventName);
        sendMessage(m);
    }

    public void sendMessage(String message) {
        communication.sendMessage(message);
    }

    public void sendMessage(Message message) {
        communication.sendMessage(message);
    }

    public Message inquireMessage(Message message) {
        return communication.inquire(message);
    }

    public IComponentClient getComponentById(int id) {
        return components.get(new Integer(id));
    }

    public int getIdByComponent(IComponentClient component) {
        Integer i = getKeyByValue(components, component);
        if (i == null) {
            return 0;
        } else {
            return i.intValue();
        }
    }

    public void setClientFactory(IClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    public IClientFactory getClientFactory() {
        return clientFactory;
    }

    //TODO: THIS METHOD MUST BE MOVED TO CORE COMMON
    @Override
    public void processMessageReceived(Message message) {
        // Define runnable interface do process message received
        class RunThread implements Runnable {

            public Communication communication;
            public Message message;

            @Override
            public void run() {
                // Travel messages receiveds
                // TODO: THIS MUST BECAME PUBLIC
                for (Message m : Message.convertToArray(message)) {
                    fireMessageReceived(m);
                    communication.finishMessageReceived(m);
                }
            }
        }
        RunThread rt = new RunThread();
        rt.message = message;
        rt.communication = communication;
        // TODO: THIS MUST BECAME PUBLIC
        doProcessMessageReceived(rt);
    }

    public abstract void doProcessMessageReceived(Runnable runnable);

    public void fireMessageReceived(Message m) {
        //for (Message m : messages) 
        {
            String n = m.getName();
            // If question application name
            if (n.equals(Protocol.MSG_NAME_INQUIRE_APPLICATION_NAME)) {
                m.setValueByName(Protocol.APPLICATION_NAME, applicationName);
            }
            // 
            if (n.equals(Protocol.MSG_NAME_SHOW_MESSAGE)) {
                doShowMessage(m.getValueByName(Protocol.TEXT_SHOW_MESSAGE));
            }
            // 
            if (n.equals(Protocol.MSG_NAME_SHOW_INPUT)) {
                // Get user input and put response at some message
                String text = doShowInput(m.getValueByName(Protocol.TEXT_SHOW_MESSAGE));
                m.setValueByName(Protocol.SHOW_INPUT_RESPONSE, text);
            }
            //
            if (n.equals(Protocol.MSG_NAME_CREATE_COMPONENT)) {
                createComponent(m);
            }
            // Get component object from id
            IComponentClient cm = null;
            if (m.isPropertyExists(Protocol.COMPONENT_ID)) {
                cm = getComponentById(m.getIntByName(Protocol.COMPONENT_ID));
                if (cm == null) {
                    if (disconnected)
                        return;
                    System.err.println("CLIENT:" + "Message:" + n + " ID not found: "
                            + m.getIntByName(Protocol.COMPONENT_ID) + " " + m.toString());
                }
            }
            // If message has component
            if (cm != null) {
                // If is change property message (create or change)
                // If found component then call changeProperty/method
                if (n.equals(Protocol.MSG_NAME_CREATE_COMPONENT) || n.equals(
                        Protocol.MSG_NAME_COMPONENT_PROPERTY_CHANGE)) {
                    cm.changeProperty(m);
                }
                // 
                cm.messageReceived(m);
                // If must destroy component
                if (n.equals(Protocol.MSG_NAME_DESTROY_COMPONENT)) {
                    cm.destroy();
                }
            }
            //
            doFireMessageReceived(m);
        }
    }
}
